//
// RS Game Framework
// Copyright © 2009 Jedd Haberstro
// jhaberstro@gmail.com
// 
// $Id:
//

#include <rs/reflection/Reflection.hpp>
#include <rs/unittest/UnitTest.hpp>
#include <cstring>

namespace
{
    using namespace rs;
    using namespace rs::reflection;
    
    class Foo
    {
        RS_DECLARE_POLYMORPHIC_REFLECTION();

    public:

        virtual ~Foo() { }

        virtual Int32 GetValue() const {
            return -1;
        }
    };

    RS_DEFINE_REFLECTION_CLASS(Foo);


    class Bar : public Foo
    {
        RS_DECLARE_REFLECTION();

    public:

        virtual Int32 GetValue() const {
            return 5;
        }
    };

    RS_DEFINE_REFLECTION_SUBCLASS(Bar, Foo);


    class Boo : public Bar
    {
        RS_DECLARE_REFLECTION();

    public:

        virtual Int32 GetValue() const {
            return 10;
        }
    };

    RS_DEFINE_REFLECTION_SUBCLASS(Boo, Bar);
    
    
    RS_TEST_CASE(rs::reflection, Class)
    {
        Foo foo;
        Bar bar;
        Boo boo;

        RS_ASSERT(Bar::ClassRecord.Equals(bar.GetClassRecord()), "class records should be equal");
        RS_ASSERT(Bar::ClassRecord == bar.GetClassRecord(), "class records should be equal");
        RS_ASSERT(!Foo::ClassRecord.Equals(bar.GetClassRecord()), "class records should be equal");
        RS_ASSERT(Foo::ClassRecord != bar.GetClassRecord(), "class records should be equal");

        RS_ASSERT(!bar.GetClassRecord().Equals(boo.GetClassRecord()), "class records should not be equal");
        RS_ASSERT(bar.GetClassRecord() != boo.GetClassRecord(), "class records should not be equal");
        RS_ASSERT(!boo.GetClassRecord().Equals(foo.GetClassRecord()), "class records should not be equal");
        RS_ASSERT(boo.GetClassRecord() != foo.GetClassRecord(), "class records should not be equal");
        RS_ASSERT(boo.GetClassRecord().Equals(boo.GetClassRecord()), "class records should be equal");
        RS_ASSERT(boo.GetClassRecord() == Boo::ClassRecord, "class records should be equal");

        Foo* fooPtr = &bar;
        RS_ASSERT(Bar::ClassRecord.Equals(fooPtr->GetClassRecord()), "class records should be equal");
        RS_ASSERT(Bar::ClassRecord == fooPtr->GetClassRecord(), "class records should be equal");
        RS_ASSERT(!Foo::ClassRecord.Equals(fooPtr->GetClassRecord()), "class records should be equal");
        RS_ASSERT(Foo::ClassRecord != fooPtr->GetClassRecord(), "class records should be equal");

        RS_ASSERT(Bar::ClassRecord.IsSubclass(Foo::ClassRecord), "Bar should be a subclass of Foo");
        RS_ASSERT(Boo::ClassRecord.IsSubclass(Foo::ClassRecord), "Boo should be a subclass of Foo");
        RS_ASSERT(Boo::ClassRecord.IsSubclass(Bar::ClassRecord), "Boo should be a subclass of Bar");
        RS_ASSERT(!Foo::ClassRecord.IsSubclass(Bar::ClassRecord), "Foo should not be a subclass of Bar");
        RS_ASSERT(!Foo::ClassRecord.IsSubclass(Boo::ClassRecord), "Foo should not be a subclass of Bar");
    }
    
    
    struct ConstructorTest
    {
        RS_DECLARE_REFLECTION();
        
        ConstructorTest()
        : integer(0),
          decimal(0.0),
          string("empty") {
        }
        
        ConstructorTest(int i)
        : integer(i),
          decimal(0.0),
          string("empty") {
        }
        
        ConstructorTest(int* i)
        : integer(*i),
        decimal(0.0),
        string("empty") {
        }
        
        ConstructorTest(int i, double d)
        : integer(i),
          decimal(d),
          string("empty") {
        }
        
        ConstructorTest(int i, double d, std::string const& str)
        : integer(i),
          decimal(d),
          string(str) {
        }
        
        Int32 integer;
        Double decimal;
        std::string string;
    };
    
    
    RS_DEFINE_REFLECTION_CLASS(ConstructorTest)
        .BindConstructor< ConstructorTest >()
        .BindConstructor< ConstructorTest, Int32 >()
        .BindConstructor< ConstructorTest, Int32* >()
        .BindConstructor< ConstructorTest, Int32, Double >()
        .BindConstructor< ConstructorTest, Int32, Double, std::string >()
        .BindAttribute("integer", &ConstructorTest::integer)
        .BindAttribute("decimal", &ConstructorTest::decimal)
        .BindAttribute("string", &ConstructorTest::string);
        
    RS_TEST_CASE(rs::reflection, Constructor)
    {
        Class const& classRecord = ReflectionCenter::GetReflectionCenter().GetClassRecord("ConstructorTest");
        
        ConstructorTest* instance = static_cast< ConstructorTest* >(classRecord.Construct());
        RS_ASSERT(instance->integer == 0, "Constructor failed");
        RS_ASSERT(instance->decimal == 0.0, "Constructor failed");
        RS_ASSERT(instance->string == std::string("empty"), "Constructor failed");
        
        delete instance;
        instance = static_cast< ConstructorTest* >(classRecord.Construct(5));
        RS_ASSERT(instance->integer == 5, "Constructor failed");
        RS_ASSERT(instance->decimal == 0.0, "Constructor failed");
        RS_ASSERT(instance->string == std::string("empty"), "Constructor failed");
        
        delete instance;
        int i = 5;
        instance = static_cast< ConstructorTest* >(classRecord.Construct(&i));
        RS_ASSERT(instance->integer == 5, "Constructor failed");
        RS_ASSERT(instance->decimal == 0.0, "Constructor failed");
        RS_ASSERT(instance->string == std::string("empty"), "Constructor failed");
        
        delete instance;
        instance = static_cast< ConstructorTest* >(classRecord.Construct(5, 10.0));
        RS_ASSERT(instance->integer == 5, "Constructor failed");
        RS_ASSERT(instance->decimal == 10.0, "Constructor failed");
        RS_ASSERT(instance->string == std::string("empty"), "Constructor failed");
        
        delete instance;
        instance = static_cast< ConstructorTest* >(classRecord.Construct(5, 10.0, std::string("test")));
        RS_ASSERT(instance->integer == 5, "Constructor failed");
        RS_ASSERT(instance->decimal == 10.0, "Constructor failed");
        RS_ASSERT(instance->string == std::string("test"), "Constructor failed");
        
        delete instance;
        instance = 0;
    }
    
    
    struct DestructorTest
    {
        RS_DECLARE_REFLECTION()
        
        static int destructionCount;
        
        DestructorTest() {
            
        }
        
        ~DestructorTest() {
            destructionCount += 1;
        }
    };
    
    int DestructorTest::destructionCount = 0;
    RS_DEFINE_REFLECTION_CLASS(DestructorTest).BindConstructor< DestructorTest >();
    
    RS_TEST_CASE(rs::reflection, Destructor)
    {
        Class const& classRecord = ReflectionCenter::GetReflectionCenter().GetClassRecord("DestructorTest");
        DestructorTest* obj = static_cast< DestructorTest* >(classRecord.Construct());
        RS_ASSERT(DestructorTest::destructionCount == 0, "destructionCount is not zero");
        classRecord.Destruct(obj);
        RS_ASSERT(DestructorTest::destructionCount == 1, "destructionCount is not one. Destruct failed");
        free(obj);
    }
    
    class OverloadedClassMethodsTest
    {
        RS_DECLARE_REFLECTION()
        
        Int32 Add(Int32 a, Int32 b) {
            return a + b;
        }
        
        Float Add(Float a, Float b) {
            return a + b;
        }
        
        Int32 Add(Int32 a, Int32 b, Int32 c) {
            return a + b + c;
        }
        
        Float Add(Float a, Float b, Float c) {
            return a + b + c;
        }
        
        Int32 Subtract(Int32 a, Int32 b) {
            return a - b;
        }
        
        Int32 Subtract(Int32 a, Int32 b, Int32 c) {
            return (a - b) - c;
        }
    };
    
    RS_DEFINE_REFLECTION_CLASS(OverloadedClassMethodsTest)
        .BindMethod("Add", static_cast< Int32 (OverloadedClassMethodsTest::*)(Int32, Int32) >(&OverloadedClassMethodsTest::Add))
        .BindMethod("Add", static_cast< Float (OverloadedClassMethodsTest::*)(Float, Float) >(&OverloadedClassMethodsTest::Add))
        .BindMethod("Add", static_cast< Int32 (OverloadedClassMethodsTest::*)(Int32, Int32, Int32) >(&OverloadedClassMethodsTest::Add))
        .BindMethod("Add", static_cast< Float (OverloadedClassMethodsTest::*)(Float, Float, Float) >(&OverloadedClassMethodsTest::Add))
        .BindMethod("Subtract", static_cast< Int32 (OverloadedClassMethodsTest::*)(Int32, Int32) >(&OverloadedClassMethodsTest::Subtract))
        .BindMethod("Subtract", static_cast< Int32 (OverloadedClassMethodsTest::*)(Int32, Int32, Int32) >(&OverloadedClassMethodsTest::Subtract));
    
    RS_TEST_CASE(rs::reflection, OverloadedClassMethods)
    {
        Int32 result = -1;
        Float result1 = -1.0f;
        OverloadedClassMethodsTest obj;
        Class const& classRecord = obj.GetClassRecord();
        
        Type const addInt2Types[2] = { Type(Types::Int32), Type(Types::Int32) }; 
        Type const addFloat2Types[2] = { Type(Types::Float), Type(Types::Float) };
        Type const addInt3Types[3] = { Type(Types::Int32), Type(Types::Int32), Type(Types::Int32) }; 
        Type const addFloat3Types[3] = { Type(Types::Float), Type(Types::Float), Type(Types::Float) };
        
        Method const& addInt2 = classRecord.GetMethodViaName("Add", 2, addInt2Types);
        addInt2(obj, 1, 1, &result);
        RS_ASSERT(addInt2.GetArgumentCount() == 2, "addInt2 argument size is incorrect");
        RS_ASSERT(addInt2.GetArgumentType(0) == addInt2Types[0], "addInt2's argument one's type is incorrect");
        RS_ASSERT(addInt2.GetArgumentType(1) == addInt2Types[1], "addInt2's argument two's type is incorrect");
        RS_ASSERT(addInt2.GetReturnType() == Type(Types::Int32), "addInt2's return type is incorrect"); 
        RS_ASSERT(result == 2, "addInt2 invocation is incorrect");
        
        Method const& addInt3 = classRecord.GetMethodViaName("Add", 3, addInt3Types);
        addInt3(obj, 1, 1, 1, &result);
        RS_ASSERT(addInt3.GetArgumentCount() == 3, "addInt3 argument size is incorrect");
        RS_ASSERT(addInt3.GetArgumentType(0) == addInt3Types[0], "addInt3's argument one's type is incorrect");
        RS_ASSERT(addInt3.GetArgumentType(1) == addInt3Types[1], "addInt3's argument two's type is incorrect");
        RS_ASSERT(addInt3.GetArgumentType(2) == addInt3Types[2], "addInt3's argument three's type is incorrect");
        RS_ASSERT(addInt3.GetReturnType() == Type(Types::Int32), "addInt3's return type is incorrect"); 
        RS_ASSERT(result == 3, "addInt3 invocation is incorrect");
        
        Method const& addFloat2 = classRecord.GetMethodViaName("Add", 2, addFloat2Types);
        addFloat2(obj, 1.0f, 1.0f, &result1);
        RS_ASSERT(addFloat2.GetArgumentCount() == 2, "addFloat2 argument size is incorrect");
        RS_ASSERT(addFloat2.GetArgumentType(0) == addFloat2Types[0], "addFloat2's argument one's type is incorrect");
        RS_ASSERT(addFloat2.GetArgumentType(1) == addFloat2Types[1], "addFloat2's argument two's type is incorrect");
        RS_ASSERT(addFloat2.GetReturnType() == Type(Types::Float), "addFloat2's return type is incorrect"); 
        RS_ASSERT(result1 == 2.0f, "addFloat2 invocation is incorrect");
        
        Method const& addFloat3 = classRecord.GetMethodViaName("Add", 3, addFloat3Types);
        addFloat3(obj, 1.0f, 1.0f, 1.0f, &result1);
        RS_ASSERT(addFloat3.GetArgumentCount() == 3, "addFloat3 argument size is incorrect");
        RS_ASSERT(addFloat3.GetArgumentType(0) == addFloat3Types[0], "addFloat3's argument one's type is incorrect");
        RS_ASSERT(addFloat3.GetArgumentType(1) == addFloat3Types[1], "addFloat3's argument two's type is incorrect");
        RS_ASSERT(addFloat3.GetArgumentType(2) == addFloat3Types[2], "addFloat3's argument three's type is incorrect");
        RS_ASSERT(addFloat3.GetReturnType() == Type(Types::Float), "addFloat3's return type is incorrect"); 
        RS_ASSERT(result1 == 3.0f, "addFloat3 invocation is incorrect");
        
        Method const& subtract2 = classRecord.GetMethodViaName("Subtract", 2);
        Method const& subtract3 = classRecord.GetMethodViaName("Subtract", 3);
        RS_ASSERT(subtract2 != subtract3, "subtract2 is equal to subtract3");
        RS_ASSERT(subtract2.GetArgumentCount() == 2, "subtract2's argument count is incorrect");
        RS_ASSERT(subtract3.GetArgumentCount() == 3, "subtract3's argument count is incorrect");
        subtract2(obj, 3, 2, &result);
        RS_ASSERT(result == 1, "subtract2 invocation is incorrect");
        subtract3(obj, 3, 2, 1, &result);
        RS_ASSERT(result == 0, "subtract3 invocation is incorrect");
    }
}
